実践!AWS CDK #18 ALB
はじめに
今回は ALB を構築します。
前回作成した EC2 をこの ALB に紐付けます。
前回の記事はこちら。
AWS 構成図
パブリックなサブネットに ALB を配置します。
設計
ALB、リスナー、ターゲットグループを作成します。
プロパティは以下の通り。
devio-stg-alb(ALB)
項目 | 値 |
---|---|
種類 | application |
スキーム | internet-facing |
IP アドレスタイプ | ipv4 |
サブネット | devio-stg-subnet-public-1a devio-stg-subnet-public-1c |
セキュリティグループ | devio-stg-sg-alb |
リスナー
項目 | 値 |
---|---|
リスナー ID | HTTP : 80 |
ルール | デフォルト: 転送先 devio-stg-tg |
devio-stg-tg(ターゲットグループ)
項目 | 値 |
---|---|
ターゲットタイプ | Instance |
プロトコル | HTTP |
ポート | 80 |
VPC | devio-stg-vpc |
ターゲット | devio-stg-ec2-1a devio-stg-ec2-1c |
実装
ALB に関する処理を行うクラスはこちら。
import * as cdk from '@aws-cdk/core'; import { CfnLoadBalancer, CfnTargetGroup, CfnListener } from '@aws-cdk/aws-elasticloadbalancingv2'; import { CfnVPC, CfnSubnet, CfnSecurityGroup, CfnInstance } from '@aws-cdk/aws-ec2'; import { Resource } from './abstract/resource'; export class Alb extends Resource { public loadBalancer: CfnLoadBalancer; private readonly vpc: CfnVPC; private readonly subnetPublic1a: CfnSubnet; private readonly subnetPublic1c: CfnSubnet; private readonly securityGroupAlb: CfnSecurityGroup; private readonly ec2Instance1a: CfnInstance; private readonly ec2Instance1c: CfnInstance; constructor( vpc: CfnVPC, subnetPublic1a: CfnSubnet, subnetPublic1c: CfnSubnet, securityGroupAlb: CfnSecurityGroup, ec2Instance1a: CfnInstance, ec2Instance1c: CfnInstance ) { super(); this.vpc = vpc; this.subnetPublic1a = subnetPublic1a; this.subnetPublic1c = subnetPublic1c; this.securityGroupAlb = securityGroupAlb; this.ec2Instance1a = ec2Instance1a; this.ec2Instance1c = ec2Instance1c; }; createResources(scope: cdk.Construct) { this.loadBalancer = this.createLoadBalancer(scope); const targetGroup = this.createTargetGroup(scope); this.createListener(scope, this.loadBalancer, targetGroup); } private createLoadBalancer(scope: cdk.Construct): CfnLoadBalancer { const loadBalancer = new CfnLoadBalancer(scope, 'Alb', { ipAddressType: 'ipv4', name: this.createResourceName(scope, 'alb'), scheme: 'internet-facing', securityGroups: [this.securityGroupAlb.attrGroupId], subnets: [this.subnetPublic1a.ref, this.subnetPublic1c.ref], type: 'application' }); return loadBalancer; } private createTargetGroup(scope: cdk.Construct): CfnTargetGroup { const targetGroup = new CfnTargetGroup(scope, 'AlbTargetGroup', { name: this.createResourceName(scope, 'tg'), port: 80, protocol: 'HTTP', targetType: 'instance', targets: [ { id: this.ec2Instance1a.ref }, { id: this.ec2Instance1c.ref } ], vpcId: this.vpc.ref }); return targetGroup; } private createListener(scope: cdk.Construct, loadBalancer: CfnLoadBalancer, targetGroup: CfnTargetGroup) { new CfnListener(scope, 'AlbListener', { defaultActions: [{ type: 'forward', forwardConfig: { targetGroups: [{ targetGroupArn: targetGroup.ref, weight: 1 }] } }], loadBalancerArn: loadBalancer.ref, port: 80, protocol: 'HTTP' }); } }
まずは ALB に関する Construct を利用するために @aws-cdk/aws-elasticloadbalancingv2
をインストールします。
$ npm install @aws-cdk/aws-elasticloadbalancingv2
@aws-cdk/aws-elasticloadbalancing
と間違えないようにご注意ください。末尾に v2
が付きます。
今回は各リソース 1 つずつなので ResourceInfo
のインタフェースは作成しません。
それぞれリソースを作成するための createXxx()
メソッドを用意し、その中でプロパティを直接設定しています。
また、動作確認をしやすいように EC2 に Apache をインストールするユーザーデータを設定しました。これで EC2 インスタンス起動時に自動的に Apache がインストールされ、Web ブラウザから ALB にアクセスすると Apache のテストページが表示されます。
#!/bin/bash sudo yum -y install httpd sudo systemctl enable httpd sudo systemctl start httpd
import * as fs from 'fs'; ~ 省略 ~ private static readonly userDataFilePath = `${__dirname}/../script/ec2/userData.sh`; ~ 省略 ~ private createInstance(scope: cdk.Construct, resourceInfo: ResourceInfo): CfnInstance { const instance = new CfnInstance(scope, resourceInfo.id, { availabilityZone: resourceInfo.availabilityZone, iamInstanceProfile: this.instanceProfileEc2.ref, imageId: Ec2.latestImageIdAmazonLinux2, instanceType: Ec2.instanceType, securityGroupIds: [this.securityGroupEc2.attrGroupId], subnetId: resourceInfo.subnetId(), tags: [{ key: 'Name', value: this.createResourceName(scope, resourceInfo.resourceName) }], userData: fs.readFileSync(Ec2.userDataFilePath, 'base64') }); return instance; }
ハイライト部分を追加しました。
やっていることは次の通りです。
- ユーザーデータのスクリプトを別ファイルに分離
- EC2 インスタンス作成時のプロパティに
userData
を追加 - ファイルから読み込むため
fs
モジュールのreadFileSync()
メソッドを利用(エンコーディングは Base64 を指定)- CFn でユーザーデータを記述する場合は Base64 でエンコードしなければならない
- [参考]:UserData | AWS CloudFormation User Guide
- ユーザーデータのファイルパスは定数化
__dirname
はカレントディレクトリの絶対パス- [参考]:__dirname | Node.js documentation
メインのプログラムはこちら。
ハイライト部分を追記しました。
import * as cdk from '@aws-cdk/core'; import { Vpc } from './resource/vpc'; import { Subnet } from './resource/subnet'; import { InternetGateway } from './resource/internetGateway'; import { ElasticIp } from './resource/elasticIp'; import { NatGateway } from './resource/natGateway'; import { RouteTable } from './resource/routeTable'; import { NetworkAcl } from './resource/networkAcl'; import { IamRole } from './resource/iamRole'; import { SecurityGroup } from './resource/securityGroup'; import { Ec2 } from './resource/ec2'; import { Alb } from './resource/alb'; export class DevioStack extends cdk.Stack { constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); // VPC const vpc = new Vpc(); vpc.createResources(this); ~ 省略 ~ // ALB const alb = new Alb( vpc.vpc, subnet.public1a, subnet.public1c, securityGroup.alb, ec2.instance1a, ec2.instance1c ); alb.createResources(this); } }
テスト
テストコードはこちら。
import { expect, countResources, haveResource, anything } from '@aws-cdk/assert'; import * as cdk from '@aws-cdk/core'; import * as Devio from '../../lib/devio-stack'; test('Alb', () => { const app = new cdk.App(); const stack = new Devio.DevioStack(app, 'DevioStack'); expect(stack).to(countResources('AWS::ElasticLoadBalancingV2::LoadBalancer', 1)); expect(stack).to(haveResource('AWS::ElasticLoadBalancingV2::LoadBalancer', { IpAddressType: 'ipv4', Name: 'undefined-undefined-alb', Scheme: 'internet-facing', SecurityGroups: anything(), Subnets: anything(), Type: 'application' })); expect(stack).to(countResources('AWS::ElasticLoadBalancingV2::TargetGroup', 1)); expect(stack).to(haveResource('AWS::ElasticLoadBalancingV2::TargetGroup', { Name: 'undefined-undefined-tg', Port: 80, Protocol: 'HTTP', TargetType: 'instance', Targets: anything(), VpcId: anything() })); expect(stack).to(countResources('AWS::ElasticLoadBalancingV2::Listener', 1)); expect(stack).to(haveResource('AWS::ElasticLoadBalancingV2::Listener', { DefaultActions: [{ Type: 'forward', ForwardConfig: { TargetGroups: [{ TargetGroupArn: anything(), Weight: 1 }] } }], LoadBalancerArn: anything(), Port: 80, Protocol: 'HTTP' })); });
以下を確認しています。
- ALB のリソースが 1 つあること
- ターゲットグループのリソースが 1 つあること
- リスナーのリソースが 1 つあること
- 各リソースのプロパティが正しいこと
確認
マネジメントコンソール上でリソースを確認してみましょう。
ALB が正しく作成されています。
お次はリスナー。
こちらもデフォルトルールでターゲットグループへ転送されるようになっていますね。
ターゲットグループも指定したインスタンスが登録されています。
Health status が unhealthy
になっていますが、これはインスタンス内に /var/www/html/index.html
を作成することで解消されます。(Apache のテストページを表示したいのでこのままにします)
そしてこちらがそのテストページです。
ALB のドメイン(ここでは devio-stg-alb-1622770308.ap-northeast-1.elb.amazonaws.com
)にアクセスすることで表示されます。
これで ALB に対する通信が EC2 までルーティングされていることがわかりますね。
これにて確認完了です!
CloudFormation 版
今回のコードを CFn で書くと以下のようになります。
Alb: Type: AWS::ElasticLoadBalancingV2::LoadBalancer Properties: IpAddressType: ipv4 Name: devio-stg-alb Scheme: internet-facing SecurityGroups: - Fn::GetAtt: - SecurityGroupAlb - GroupId Subnets: - Ref: SubnetPublic1a - Ref: SubnetPublic1c Type: application AlbTargetGroup: Type: AWS::ElasticLoadBalancingV2::TargetGroup Properties: Name: devio-stg-tg Port: 80 Protocol: HTTP Targets: - Id: Ref: Ec2Instance1a - Id: Ref: Ec2Instance1c TargetType: instance VpcId: Ref: Vpc AlbListener: Type: AWS::ElasticLoadBalancingV2::Listener Properties: DefaultActions: - ForwardConfig: TargetGroups: - TargetGroupArn: Ref: AlbTargetGroup Weight: 1 Type: forward LoadBalancerArn: Ref: Alb Port: 80 Protocol: HTTP
なお、EC2 の UserData に関しては Base64 でエンコードしたので、AWS CDK からのテンプレート出力では次のように表示されてしまいます。
Ec2Instance1a: Type: AWS::EC2::Instance Properties: AvailabilityZone: ap-northeast-1a IamInstanceProfile: Ref: InstanceProfileEc2 ImageId: ami-06631ebafb3ae5d34 InstanceType: t2.micro SecurityGroupIds: - Fn::GetAtt: - SecurityGroupEc2 - GroupId SubnetId: Ref: SubnetApp1a Tags: - Key: Name Value: devio-stg-ec2-1a UserData: IyEvYmluL2Jhc2gKc3VkbyB5dW0gLXkgaW5zdGFsbCBodHRwZApzdWRvIHN5c3RlbWN0bCBlbmFibGUgaHR0cGQKc3VkbyBzeXN0ZW1jdGwgc3RhcnQgaHR0cGQK
このタイミングで中身が見れないのは残念ですが、確認したいときはファイルを見に行くようにしましょう。
GitHub
今回のソースコードは コチラ です。
おわりに
ALB を設置することによってインターネット経由のアクセスで動作確認ができるようになりました。
マネジメントコンソール以外で確認できるのは嬉しいですね。ようやく 構築できてるんだな ということが実感できます。
ではゴールまでもう少し、次回からは DB まわりを構築していきましょう。
リンク
- class CfnLoadBalancer (construct) | AWS CDK API Reference
- class CfnTargetGroup (construct) | AWS CDK API Reference
- class CfnListener (construct) | AWS CDK API Reference
- AWS::ElasticLoadBalancingV2::LoadBalancer | AWS CloudFormation User Guide
- AWS::ElasticLoadBalancingV2::TargetGroup | AWS CloudFormation User Guide
- AWS::ElasticLoadBalancingV2::Listener | AWS CloudFormation User Guide
- AWS::EC2::Instance | AWS CloudFormation User Guide
- Application Load Balancer とは? | Elastic Load Balancing
- Application Load Balancer | Elastic Load Balancing
- Application Load Balancer のリスナー | Elastic Load Balancing
- Application Load Balancer のターゲットグループ | Elastic Load Balancing
- 起動時に Linux インスタンスでコマンドを実行する | Amazon EC2 User Guide
- Compiling and Installing | The Apache HTTP Server Project
- Modules: CommonJS modules | Node.js documentation